(in-package :experiment)

(define-condition audio-error (simple-condition)
  ())

(defstruct recorder-stream
  pipe error)

(defclass audio-record (subsequence)
  ((audio-stream :initform nil :accessor audio-stream)))

(defmethod state-init (exp (state audio-record))
  (handler-case
      (setf (audio-stream state) (start-audio-recording (experiment-id exp)))
    (audio-error (c)
      (sdl:fill-surface (gwee:gray 0))
      (gwee:display-message (format nil "Failed to initialize audio: ~a" c))
      nil))
  (lg 'start :index (subsequence-index state)))

(defmethod state-clean-up (exp (state audio-record))
  (lg 'end)
  (when (audio-stream state)
    (end-audio-recording (audio-stream state))))

(defun find-recorder-program ()
  (let ((program (gwee:resource "RecordAudioToFile")))
    (when (probe-file program)
      program)))

(defun current-datetime (&optional (time-delim ":"))
  (multiple-value-bind (s m h d mm y) (get-decoded-time)
    (format nil "~d-~2,'0d-~2,'0d--~2,'0d~a~2,'0d~a~2,'0d" y mm d h time-delim m time-delim s)))

#+lispworks
(defun start-audio-recording (id)
  (let ((program (find-recorder-program)))
    (if program
        (progn
          (multiple-value-bind (pipe err)
              (system:run-shell-command (format nil "~a ~a ~a"
                                                (namestring program)
                                                (gwee:top-level-path "audio/")
                                                (format nil "audio-~a--~a.mp4" id (current-datetime "-")))
                                        :wait nil
                                        :output :stream
                                        :input :stream
                                        :error-output :stream)
            ;; the program will print errors to err and if no error occurs, it
            ;; will print a line to pipe.
            (loop
               with start = (gwee:now)
               until (or (listen pipe)
                         (listen err)
                         (> (/ (- start (gwee:now))
                               internal-time-units-per-second) 5))
               do (sdl-cffi::sdl-delay 100))
            ;; we can listen on pipe first because the only things
            ;; coming out of the recorder's stdout is the begin message
            ;; which is printed from the audio data callback. So if we
            ;; get that message we KNOW we are recording. Anything on
            ;; the err pipe is a buffer under-run.
            (cond ((listen err)
                   (signal 'audio-error :format-control "Audio Error: ~a" :format-arguments (list (read-line err))))
                  ((listen pipe)
                   (let ((line (read-line pipe)))
                     (unless (string= line "hit a key to stop.")
                       (signal 'audio-error :format-control "Audio Error: ~a" :format-arguments (list line)))))
                  (t
                   (signal 'audio-error :format-control "I waited 5 seconds for the recorder but it never booted!")))
            (make-recorder-stream :pipe pipe :error err)))
        (signal 'audio-error :format-control "Unable to find recording program."))))

#-(or lispworks)
(defun start-audio-recording (id)
  (signal 'audio-error :format-control "Audio recording is only supported in lispworks"))

(defun end-audio-recording (stream)
  (write-line "stop." (recorder-stream-pipe stream))
  ;; we need to push this out to ensure the audio stops.
  (finish-output (recorder-stream-pipe stream)))
